namespace createjs {


	export class DisplayObject extends EventDispatcher {

		static _StageGL_NONE = 0;
		static _StageGL_SPRITE = 1;

		parent?: Container;
		alpha: number;
		id: number;
		tickEnabled: boolean;
		name: string;
		regX: number;
		regY: number;
		rotation: number;
		scaleX: number;
		scaleY: number;
		skewX: number;
		skewY: number;
		visible: boolean;
		x: number;
		y: number;
		transformMatrix: any;
		_props: DisplayProps;
		_rectangle: Rectangle;
		_bounds: any;
		_webGLRenderStyle: number;
		_glMtx: Matrix2D;

		constructor() {
			super();
			this.alpha = 1;
			this.id = createjs.UID.get();

			this.tickEnabled = true;

			this.name = null;
			this.parent = null;

			this.regX = 0;
			this.regY = 0;
			this.rotation = 0;
			this.scaleX = 1;
			this.scaleY = 1;
			this.skewX = 0;
			this.skewY = 0;
			this.visible = true;
			this.x = 0;
			this.y = 0;

			this.transformMatrix = null;

			this._props = new createjs.DisplayProps();
			this._rectangle = new createjs.Rectangle();
			this._bounds = null;
			this._webGLRenderStyle = DisplayObject._StageGL_NONE;
			this._glMtx = new createjs.Matrix2D();
		}

		getStage() {
			// uses dynamic access to avoid circular dependencies;
			var o: DisplayObject = this, _Stage = createjs.StageGL;
			while (o.parent) { o = o.parent; }
			if (o instanceof _Stage) { return o; }
			return null;
		};

		get stage() {
			return this.getStage();
		}

		set scale(scale) {
			this.scaleX = this.scaleY = scale;
		}
		get scale() {
			return this.scaleX;
		}

		isVisible() {
			return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0);
		};

		localToGlobal(x, y, pt?) {
			return this.getConcatenatedMatrix(this._props.matrix).transformPoint(x, y, pt || new createjs.Point());
		};


		globalToLocal(x, y, pt?) {
			return this.getConcatenatedMatrix(this._props.matrix).invert().transformPoint(x, y, pt || new createjs.Point());
		};

		localToLocal(x, y, target, pt?) {
			pt = this.localToGlobal(x, y, pt);
			return target.globalToLocal(pt.x, pt.y, pt);
		};


		setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {
			this.x = x || 0;
			this.y = y || 0;
			this.scaleX = scaleX == null ? 1 : scaleX;
			this.scaleY = scaleY == null ? 1 : scaleY;
			this.rotation = rotation || 0;
			this.skewX = skewX || 0;
			this.skewY = skewY || 0;
			this.regX = regX || 0;
			this.regY = regY || 0;
			return this;
		};

		getMatrix(matrix) {
			var o = this, mtx = matrix || new createjs.Matrix2D();
			return o.transformMatrix ? mtx.copy(o.transformMatrix) :
				(mtx.identity() && mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY));
		};


		getConcatenatedMatrix(matrix) {
			var o: DisplayObject = this, mtx = this.getMatrix(matrix);
			while (o = o.parent) {
				mtx.prependMatrix(o.getMatrix(o._props.matrix));
			}
			return mtx;
		};


		getConcatenatedDisplayProps(props: DisplayProps) {
			props = props ? props.identity() : new createjs.DisplayProps();
			var o: DisplayObject = this, mtx = o.getMatrix(props.matrix);
			do {
				props.prepend(o.visible, o.alpha);

				// we do this to avoid problems with the matrix being used for both operations when o._props.matrix is passed in as the props param.
				// this could be simplified (ie. just done as part of the prepend above) if we switched to using a pool.
				if (o != this) {
					mtx.prependMatrix(o.getMatrix(o._props.matrix));
				}
			} while (o = o.parent);
			return props;
		};

		getBounds() {
			if (this._bounds) {
				return this._rectangle.copy(this._bounds);
			}
			return null;
		};

		getTransformedBounds() {
			return this._getBounds();
		};

		setBounds(x, y, width, height) {
			if (x == null) { this._bounds = x; return; }
			this._bounds = (this._bounds || new createjs.Rectangle()).setValues(x, y, width, height);
		};


		clone(recursive?: boolean) {
			return this._cloneProps(new DisplayObject());
		};


		toString() {
			return "[DisplayObject (name=" + this.name + ")]";
		};



		_cloneProps(o: DisplayObject) {
			o.alpha = this.alpha;
			o.tickEnabled = this.tickEnabled;
			o.name = this.name;
			o.regX = this.regX;
			o.regY = this.regY;
			o.rotation = this.rotation;
			o.scaleX = this.scaleX;
			o.scaleY = this.scaleY;
			o.skewX = this.skewX;
			o.skewY = this.skewY;
			o.visible = this.visible;
			o.x = this.x;
			o.y = this.y;
			o._bounds = this._bounds;
			o._webGLRenderStyle = this._webGLRenderStyle;
			return o;
		};


		_tick(evtObj: Event) {
			// because tick can be really performance sensitive, check for listeners before calling dispatchEvent.
			var ls = this._listeners;
			if (ls && ls["tick"]) {
				// reset & reuse the event object to avoid construction / GC costs:
				evtObj.target = null;
				evtObj.propagationStopped = evtObj.immediatePropagationStopped = false;
				this.dispatchEvent(evtObj);
			}
		};

		_getBounds(matrix?, ignoreTransform?) {
			return this._transformBounds(this.getBounds(), matrix, ignoreTransform);
		};


		_transformBounds(bounds: Rectangle, matrix: Matrix2D, ignoreTransform?: boolean) {
			if (!bounds) { return bounds; }
			var x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height, mtx = this._props.matrix;
			mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx);

			if (x || y) { mtx.appendTransform(0, 0, 1, 1, 0, 0, 0, -x, -y); } // TODO: simplify this.
			if (matrix) { mtx.prependMatrix(matrix); }

			var x_a = width * mtx.a, x_b = width * mtx.b;
			var y_c = height * mtx.c, y_d = height * mtx.d;
			var tx = mtx.tx, ty = mtx.ty;

			var minX = tx, maxX = tx, minY = ty, maxY = ty;

			if ((x = x_a + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; }
			if ((x = x_a + y_c + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; }
			if ((x = y_c + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; }

			if ((y = x_b + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; }
			if ((y = x_b + y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; }
			if ((y = y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; }

			return bounds.setValues(minX, minY, maxX - minX, maxY - minY);
		};

		_updateState() {
		};

	}

}